/*
 * Copyright 2011 Dim
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.ratingviewer.parser;

import java.text.*;
import java.util.*;
import java.util.regex.*;

import org.htmlparser.Parser;
import org.htmlparser.filters.NodeClassFilter;
import org.htmlparser.lexer.*;
import org.htmlparser.tags.*;
import org.htmlparser.util.*;
import org.ratingviewer.data.*;
import org.ratingviewer.data.source.DataSource;

public class BeregDataParser {

	private static final String LASTGAMETIME_DATEFORMAT_PATTERN = "dd.MM.yyyy";
	private static final String RATING_PATTERN = "^[^\\d]*(\\d+)[^(]*(?:\\((\\d+),[^\\d]+(\\d{2}\\.\\d{2}\\.\\d{4})\\)[^)]*)?$";

	private static final String LINK_PLAYER_PROFILE_IDPREFIX = "?id=";
	private static final String IMAGETAG_TEXT_ATTRIBUTE = "ALT";

	// Players table columns
	private static final int PLAYER_COLUMNCOUNT = 12;
	private static final int PLAYER_RANK_COLUMN = 0;
	private static final int PLAYER_TITLE_COLUMN = 1;
	private static final int PLAYER_PROFILE_COLUMN = 2;
	private static final int PLAYER_RATING_COLUMN = 3;
	private static final int PLAYER_QUICKCHESS_COLUMN = 4;
	private static final int PLAYER_GAMES_COLUMN = 5;
	private static final int PLAYER_WIN_COLUMN = 6;
	private static final int PLAYER_DRAW_COLUMN = 7;
	private static final int PLAYER_DEFEAT_COLUMN = 8;
	private static final int PLAYER_LASTGAMETIME_COLUMN = 9;

	private final DataSource dataSource;

	public BeregDataParser(DataSource dataSource) {
		super();

		if (dataSource == null)
			throw new IllegalArgumentException("DataSource can't be null.");
		this.dataSource = dataSource;
	}

	public List<Player> findPlayer(String caseSensitiveName) throws PageGetException, PageParseException {
		Page playersPage = dataSource.findPlayer(caseSensitiveName);
		return findPlayer(playersPage);
	}

	public List<Player> findPlayer(Page playersPage) throws PageGetException, PageParseException {
		TableTag playersTable = getPlayersTable(playersPage);
		if (playersTable == null)
			throw new PageDataNotFoundException("Players table not found on HTML page.");

		List<Player> matchedPlayers = new LinkedList<Player>();
		int rowCount = playersTable.getRowCount();

		DateFormat lastGameTimeFormat = new SimpleDateFormat(LASTGAMETIME_DATEFORMAT_PATTERN);
		Pattern ratingPattern = Pattern.compile(RATING_PATTERN);
		Matcher ratingMatcher = ratingPattern.matcher("");

		for (int i = 1; i < rowCount - 1; i++) {
			TableRow nextRow = playersTable.getRow(i);

			TableColumn[] columns = nextRow.getColumns();
			if (columns.length >= PLAYER_COLUMNCOUNT) {
				Player nextPlayer = createPlayer(columns[PLAYER_RANK_COLUMN], columns[PLAYER_PROFILE_COLUMN]);
				matchedPlayers.add(nextPlayer);

				try {
					ImageTag imageTag = ParserUtils.getFirstImageNode(columns[PLAYER_TITLE_COLUMN]);
					String title = imageTag.getAttribute(IMAGETAG_TEXT_ATTRIBUTE);
					nextPlayer.setTitle(title);
				} catch (PageDataNotFoundException ex) {
					// ignored
				}

				try {
					String ratingDesc = ParserUtils.getText(columns[PLAYER_RATING_COLUMN]);
					if (ratingMatcher.reset(ratingDesc).matches()) {
						String maybeRatingNumber = ratingMatcher.group(1);
						int rating = Integer.parseInt(maybeRatingNumber);
						nextPlayer.setRating(rating);
					}
				} catch (PageDataNotFoundException ex) {
					// ignored
				} catch (NumberFormatException ex) {
					// ignored
				}

				try {
					String quickChessDesc = ParserUtils.getText(columns[PLAYER_QUICKCHESS_COLUMN]);
					if (ratingMatcher.reset(quickChessDesc).matches()) {
						String maybeRatingNumber = ratingMatcher.group(1);
						int rating = Integer.parseInt(maybeRatingNumber);
						nextPlayer.setQuickChessRating(rating);
					}
				} catch (PageDataNotFoundException ex) {
					// ignored
				} catch (NumberFormatException ex) {
					// ignored
				}

				try {
					int gamesCount = ParserUtils.getInt(columns[PLAYER_GAMES_COLUMN]);
					nextPlayer.setGamesCount(gamesCount);
				} catch (PageDataNotFoundException ex) {
					// ignored
				}

				try {
					int winCount = ParserUtils.getInt(columns[PLAYER_WIN_COLUMN]);
					nextPlayer.setWinCount(winCount);
				} catch (PageDataNotFoundException ex) {
					// ignored
				}

				try {
					int drawCount = ParserUtils.getInt(columns[PLAYER_DRAW_COLUMN]);
					nextPlayer.setDrawnCount(drawCount);
				} catch (PageDataNotFoundException ex) {
					// ignored
				}

				try {
					int defeatCount = ParserUtils.getInt(columns[PLAYER_DEFEAT_COLUMN]);
					nextPlayer.setDefeatCount(defeatCount);
				} catch (PageDataNotFoundException ex) {
					// ignored
				}

				try {
					Date lastGameDate = lastGameTimeFormat.parse(ParserUtils.getText(columns[PLAYER_LASTGAMETIME_COLUMN]));
					nextPlayer.setLastGameTime(lastGameDate.getTime());
				} catch (ParseException ex) {
					// ignored
				}
			}
		}

		return matchedPlayers;
	}

	private Player createPlayer(TableColumn rankColumn, TableColumn profileColumn)
			throws PageDataNotFoundException, PageParseException {
		LinkTag profileLink = ParserUtils.getFirstLinkNode(profileColumn);
		String link = profileLink.getLink();

		int playerIdIndex = link.lastIndexOf(LINK_PLAYER_PROFILE_IDPREFIX);
		if (playerIdIndex < 0)
			throw new PageParseException(MessageFormat.format("Invalid link format to player "
					+ "profile: ''{0}''.", link));

		try {
			String maybeNumber = link.substring(playerIdIndex + LINK_PLAYER_PROFILE_IDPREFIX.length());
			long id = Long.parseLong(maybeNumber);
			String name = Translate.decode(profileLink.getLinkText());
			int rank = ParserUtils.getInt(rankColumn);

			return new Player(id, rank, name);
		} catch (NumberFormatException ex) {
			throw new PageParseException(MessageFormat.format("Invalid player ID format: {0}.",
					ex.getLocalizedMessage()), ex);
		}
	}

	private TableTag getPlayersTable(Page playersPage) throws PageParseException {
		TableTag playersTableNode = null;
		try {
			Lexer lexer = new Lexer(playersPage);
			Parser htmlParser = new Parser(lexer);
			NodeList tableTags = htmlParser.extractAllNodesThatMatch(new NodeClassFilter(TableTag.class));

			playersTableNode = null;
			SimpleNodeIterator tableNodes = tableTags.elements();
			while (playersTableNode == null && tableNodes.hasMoreNodes()) {
				TableTag nextTable = (TableTag) tableNodes.nextNode();
				int rowCount = nextTable.getRowCount();
				if (rowCount > 0) {
					TableRow headerRow = nextTable.getRow(0);
					int columnCount = headerRow.getColumnCount();
					if (columnCount == PLAYER_COLUMNCOUNT)
						playersTableNode = nextTable;
				}
			}
		} catch (ParserException ex) {
			throw new PageParseException("Can't parse HTML page", ex);
		}

		return playersTableNode;
	}
}
